/**
 * 
 */
package de.bomzhi.mz.pflection.model;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.powerflasher.fdt.core.builder.IDependencyTracker;
import com.powerflasher.fdt.core.dom.IASTVisitor;
import com.powerflasher.fdt.core.dom.IBasicFunctionDeclaration;
import com.powerflasher.fdt.core.dom.IBlock;
import com.powerflasher.fdt.core.dom.IBreakStatement;
import com.powerflasher.fdt.core.dom.ICase;
import com.powerflasher.fdt.core.dom.IConditionalExpression;
import com.powerflasher.fdt.core.dom.IContinueStatement;
import com.powerflasher.fdt.core.dom.IDeclaration;
import com.powerflasher.fdt.core.dom.IDeclarationBindable;
import com.powerflasher.fdt.core.dom.IIfStatement;
import com.powerflasher.fdt.core.dom.ILoop;
import com.powerflasher.fdt.core.dom.INode;
import com.powerflasher.fdt.core.dom.IReturnStatement;
import com.powerflasher.fdt.core.dom.IReturnType;
import com.powerflasher.fdt.core.dom.IStatement;
import com.powerflasher.fdt.core.dom.IThrowStatement;
import com.powerflasher.fdt.core.dom.ITryStatement;
import com.powerflasher.fdt.core.outermodel.AbstractSourceUnitContainerModel;
import com.powerflasher.fdt.core.outermodel.IClassModel;
import com.powerflasher.fdt.core.outermodel.IFieldModel;
import com.powerflasher.fdt.core.outermodel.IFunctionModel;
import com.powerflasher.fdt.core.outermodel.IGetterModel;
import com.powerflasher.fdt.core.outermodel.IInterfaceModel;
import com.powerflasher.fdt.core.outermodel.IMasterClassModel;
import com.powerflasher.fdt.core.outermodel.IMasterFunctionModel;
import com.powerflasher.fdt.core.outermodel.IMasterInterfaceModel;
import com.powerflasher.fdt.core.outermodel.IMasterModel;
import com.powerflasher.fdt.core.outermodel.IMasterNamespaceModel;
import com.powerflasher.fdt.core.outermodel.IMasterVariableModel;
import com.powerflasher.fdt.core.outermodel.IMemberNamespaceModel;
import com.powerflasher.fdt.core.outermodel.IMethodModel;
import com.powerflasher.fdt.core.outermodel.INamespaceModel;
import com.powerflasher.fdt.core.outermodel.IOuterModelElement;
import com.powerflasher.fdt.core.outermodel.IOuterModelVisitor;
import com.powerflasher.fdt.core.outermodel.ISetterModel;
import com.powerflasher.fdt.core.outermodel.ITypeModel;
import com.powerflasher.fdt.core.outermodel.IVariableModel;
import com.powerflasher.fdt.core.outermodel.MetaDataTagModel;
import com.powerflasher.fdt.core.outermodel.PackageModel;
import com.powerflasher.fdt.core.outermodel.ParameterModel;
import com.powerflasher.fdt.core.outermodel.SWCModel;
import com.powerflasher.fdt.core.outermodel.SourceFolderModel;
import com.powerflasher.fdt.core.outermodel.as3.IAS3LocalClassModel;
import com.powerflasher.fdt.core.outermodel.as3.IAS3LocalFunctionModel;
import com.powerflasher.fdt.core.outermodel.as3.IAS3LocalInterfaceModel;
import com.powerflasher.fdt.core.outermodel.as3.IAS3LocalNamespaceModel;
import com.powerflasher.fdt.core.outermodel.as3.IAS3LocalVariableModel;
import com.powerflasher.fdt.core.outermodel.binding.Binding;
import com.powerflasher.fdt.core.resources.IFlashProject;

public final class PflectionValuesComputer implements IOuterModelVisitor {
	public static class FunctionAnalyser implements IASTVisitor {
		private int numberOfConditions = 0;
		private int numberOfBlocks = 0;
		private int numberOfFlowTerminations = 0;
		private int numberOfInternalReferences = 0;

		public boolean visitLeave(INode arg0) {
			return false;
		}

		public boolean visitEnter(INode node) {
			if (node instanceof ILoop || node instanceof IIfStatement
					|| node instanceof IConditionalExpression
					|| node instanceof ITryStatement || node instanceof ICase) {
				numberOfConditions++;
			}
			if (node instanceof IBlock) {
				numberOfBlocks++;
			}
			if (node instanceof IReturnStatement
					|| node instanceof IBreakStatement
					|| node instanceof IContinueStatement
					|| node instanceof IThrowStatement) {
				numberOfFlowTerminations++;
			}
			if (node instanceof IBasicFunctionDeclaration) {
				IBasicFunctionDeclaration functionDeclaration = (IBasicFunctionDeclaration) node;
				IReturnType returnType = functionDeclaration.getReturnType();
				if (returnType != null && returnType.isVoid()) {
					numberOfFlowTerminations++;
				}
			}
			if (node instanceof IDeclarationBindable
					&& !(node instanceof IDeclaration)) {
				IOuterModelElement declarationTarget = ((IDeclarationBindable) node)
						.getDeclarationTarget();
				IMasterModel masterModel = node.getMasterModel();
				IFunctionModel functionModel = getFunctionModel(node);
				IOuterModelElement searchModel = declarationTarget;
				while (searchModel != null
						&& !(searchModel instanceof AbstractSourceUnitContainerModel)) {
					if (masterModel.equals(searchModel)) {
						numberOfInternalReferences++;
						break;
					}
					if (searchModel.equals(functionModel)) {
						break;
					}
					searchModel = searchModel.getParent();
				}
			}
			return true;
		}

		private IFunctionModel getFunctionModel(INode node) {
			INode searchNode = node;
			while (searchNode != null) {
				if (searchNode instanceof IBasicFunctionDeclaration) {
					Binding declarationBinding = ((IBasicFunctionDeclaration) searchNode)
							.getDeclarationBinding();
					return (IFunctionModel) declarationBinding.getTarget();
				}
				searchNode = searchNode.getNodeParent();
			}
			return null;
		}

		public int getNumberOfConditions() {
			return numberOfConditions;
		}

		public int getNumberOfBlocks() {
			return numberOfBlocks;
		}

		public int getNumberOfFlowTerminations() {
			return numberOfFlowTerminations;
		}

		public int getNumberOfInternalReferences() {
			return numberOfInternalReferences;
		}
	}

	private Set<IOuterModelElement> visitedElements = new HashSet<IOuterModelElement>();
	private PflectionValues values = new PflectionValues();

	public boolean visitLeave(IOuterModelElement arg0) {
		return false;
	}

	public boolean visitEnter(IOuterModelElement element) {
		if (visitedElements.contains(element)) {
			return false;
		}
		visitedElements.add(element);

		// TODO uncomment when it's clear how to do metric representation
		// values.addName(element);

		if (element instanceof SourceFolderModel) {
			values.sourceFolders.add((SourceFolderModel) element);
		} else if (element instanceof PackageModel) {
			values.packages.add((PackageModel) element);
		} else if (element instanceof SWCModel) {
			values.SWCs.add((SWCModel) element);
		} else if (element instanceof IMasterClassModel) {
			values.classes.add((IClassModel) element);
		} else if (element instanceof IMasterInterfaceModel) {
			values.interfaces.add((IInterfaceModel) element);
		} else if (element instanceof IMasterFunctionModel) {
			values.functions.add((IFunctionModel) element);
		} else if (element instanceof IMasterVariableModel) {
			if (((IVariableModel) element).isConstant()) {
				values.constants.add((IVariableModel) element);
			} else {
				values.variables.add((IVariableModel) element);
			}
		} else if (element instanceof IMasterNamespaceModel) {
			values.namespaces.add((INamespaceModel) element);
		} else if (element instanceof IAS3LocalClassModel) {
			values.localClasses.add((IAS3LocalClassModel) element);
		} else if (element instanceof IAS3LocalInterfaceModel) {
			values.localInterfaces.add((IAS3LocalInterfaceModel) element);
		} else if (element instanceof IAS3LocalFunctionModel) {
			values.localFunctions.add((IAS3LocalFunctionModel) element);
		} else if (element instanceof IAS3LocalVariableModel) {
			if (((IVariableModel) element).isConstant()) {
				values.localConstants.add((IAS3LocalVariableModel) element);
			} else {
				values.localVariables.add((IAS3LocalVariableModel) element);
			}
		} else if (element instanceof IAS3LocalNamespaceModel) {
			values.localNamespaces.add((IAS3LocalNamespaceModel) element);
		} else if (element instanceof IMethodModel) {
			if (((IMethodModel) element).isStatic()) {
				values.staticMethods.add((IMethodModel) element);
			} else {
				values.methods.add((IMethodModel) element);
			}
		} else if (element instanceof ISetterModel) {
			values.setter.add((ISetterModel) element);
		} else if (element instanceof IGetterModel) {
			values.getter.add((IGetterModel) element);
		} else if (element instanceof IFieldModel) {
			if (((IVariableModel) element).isConstant()) {
				values.constantField.add((IFieldModel) element);
			} else {
				values.field.add((IFieldModel) element);
			}
		} else if (element instanceof INamespaceModel) {
			values.memberNamespaces.add((IMemberNamespaceModel) element);
		} else if (element instanceof MetaDataTagModel) {
			values.metaTags.add((MetaDataTagModel) element);
		}

		if (element instanceof IFunctionModel) {
			List<ParameterModel> parameters = ((IFunctionModel) element)
					.getParameters();
			for (ParameterModel parameterModel : parameters) {
				values.addName(parameterModel);
			}
			values.addParamValue(parameters.size(), (IFunctionModel) element);
			if (!(element.getParent() instanceof IFunctionModel)
					|| element.getParent() instanceof IAS3LocalFunctionModel) {
				int mloc = computeMLOC((IFunctionModel) element);
				if (mloc > 0) {
					values.addMLOC(mloc, (IFunctionModel) element);
				}
				computeNumbersForFunction((IFunctionModel) element);

			}
			if (parameters.size() == 1
					&& ((IFunctionModel) element).getTypeBinding() == Binding.NO_REF) {
				ITypeModel typeTarget = parameters.get(0).getTypeTarget();
				if (typeTarget instanceof IClassModel) {
					List<IClassModel> parents = ((IClassModel) typeTarget)
							.getSuperTargetsIncludingInherited();
					for (IClassModel parent : parents) {
						if (parent instanceof IMasterClassModel) {
							IMasterClassModel masterClass = (IMasterClassModel) parent;
							if (masterClass.getQualifiedName()
									.equals("flash.events.Event")) {
								values.eventListeners.add((IFunctionModel)element);
							}
						}
					}
				}
			}
		} else if (element instanceof IInterfaceModel) {
			values.addImplementationValue(((IInterfaceModel) element)
					.getImplementorTargets().size(), (IInterfaceModel) element);
		} else if (element instanceof IClassModel) {
			values.addInheritansDepthValue(((IClassModel) element)
					.getSuperTargetsIncludingInherited().size(),
					(IClassModel) element);
		}
		if (element instanceof IMasterModel) {
			IFlashProject flashProject = (IFlashProject) element
					.getProjectModel().getProject().getAdapter(
							IFlashProject.class);
			IDependencyTracker dependencyTracker = flashProject
					.getServiceFactory().getDependencyTracker();
			Set<Binding> outgoing = dependencyTracker.getLinksTo(element
					.getDeclarationBinding());
			values.addOutgoing(outgoing.size(), (IMasterModel) element);
			Set<Binding> incoming = dependencyTracker.getLinksFrom(element
					.getDeclarationBinding());
			values.addIncoming(incoming.size(), (IMasterModel) element);
		}
		return true;
	}

	private void computeNumbersForFunction(IFunctionModel functionModel) {
		Object source = functionModel.getSource();
		if (source instanceof IBasicFunctionDeclaration) {
			IBasicFunctionDeclaration functionDeclaration = (IBasicFunctionDeclaration) source;
			FunctionAnalyser analyser = new FunctionAnalyser();
			functionDeclaration.accept(analyser);
			int numberOfConditions = analyser.getNumberOfConditions();
			if (numberOfConditions > 0) {
				values.addNumberOfConditions(numberOfConditions, functionModel);
			}
			int numberOfBlocks = analyser.getNumberOfBlocks();
			if (numberOfBlocks > 1) {
				values.addNumberOfBlocks(numberOfBlocks, functionModel);
			}
			int numberOfFlowTerminations = analyser
					.getNumberOfFlowTerminations();
			if (numberOfFlowTerminations > 0) {
				values.addNumberOfFlowTerminations(numberOfFlowTerminations,
						functionModel);
			}
			if (functionDeclaration.getBlock() != null) {
				int numberOfInternalReferences = analyser
						.getNumberOfInternalReferences();
				values.addNumberOfInternalReferences(
						numberOfInternalReferences, functionModel);
			}
			return;
		}
	}

	public static int computeMLOC(IFunctionModel functionModel) {
		Object source = functionModel.getSource();
		if (source instanceof IBasicFunctionDeclaration) {
			IBlock block = ((IBasicFunctionDeclaration) source).getBlock();
			if (block == null) {
				return 0;
			}
			List<IStatement> statementList = block.getStatements();
			if (!statementList.isEmpty()) {
				return (statementList.get(statementList.size() - 1)
						.getEndLine() - statementList.get(0).getBeginLine()) + 1;
			}
		}
		return 0;
	}

	public PflectionValues getValues() {
		return values;
	}
}